#include "head.h"

extern char path[512];

//是否秒传文件
//返回值，0代表秒传成功，-1代表查询失败，-2代表用户在上传重复的文件
static int is_instant_transmit(client_info_t* pclient_info, MYSQL* mysql, char *file_hash);
//判断是否需要续传
//is_bad 判断当前上传的文件是否是上次断掉的文件
static int is_need_continuation(MYSQL* mysql, client_info_t* pclient_info, int *is_bad, char *file_hash);
//puts服务端接收文件
static bool puts_recvfile(client_info_t* pclient_info, char *file_hash, int *is_bad);
//上传了一个新文件，新建一条新数据
static bool insert_newfile(client_info_t* pclient_info, MYSQL* mysql, char *file_hash, int is_bad);



//***************** 长命令处理函数 ***********************
// ！！！！！！！长命令需要在完成任务后在函数体内部关闭网络文件描述符！！！！！！！
int puts_handler(client_info_t* pclient_info, MYSQL* mysql){
    //先从客户端拿到需要上传文件的hash值
    char file_hash[41] = { 0 };
    recv_train(file_hash, pclient_info->net_fd);

    int ret = is_instant_transmit(pclient_info, mysql, file_hash);
    if(ret == 0){
        //向客户端发送提示，极速秒传成功                    
        send_train("极速秒传成功！", pclient_info->net_fd);
        printf("已通过极速秒传成功上传文件！\n");
        //别忘了关闭网络文件描述符
        close(pclient_info->net_fd);

        return 0;
    }  
    if(ret == -2){
        //用户再上传重复的文件，直接给用户进行提示
        send_train("当前路径下已有该文件，上传失败!", pclient_info->net_fd);
        printf("用户在上传重复的文件，已返回失败！\n");
        //别忘了关闭网络文件描述符
        close(pclient_info->net_fd);
        return 0;
    }

    //运行到这里，说明不存在此文件，就执行上传逻辑
    //判断是否是损坏文件的续传
    int is_bad = 0;
    //is_need_continuation(mysql, pclient_info, &is_bad, file_hash);
    send_train("正在上传文件，请稍等...", pclient_info->net_fd);

    if(puts_recvfile(pclient_info, file_hash, &is_bad)){
        //服务器接收文件成功
        //接收完完文件记得插入新的数据       
        if(insert_newfile(pclient_info, mysql, file_hash, is_bad)){
            send_train("文件上传成功！\n", pclient_info->net_fd);
            printf("文件上传成功！\n");
        }else{
            printf("接收文件成功，但插入数据失败!\n");
            //关闭网络文件描述符
            close(pclient_info->net_fd);
            return 0;
        }
    }

    //关闭网络文件描述符
    close(pclient_info->net_fd);
    return 0;
}

int gets_handler(client_info_t* pclient_info, MYSQL* mysql){
    printf("开始执行gets_handler\n");
    int net_fd = pclient_info -> net_fd;
    int len = 0;
    char buf[256] = {0};
    //接收客户端请求的文件
    int ret = recv(net_fd,&len,sizeof(int),MSG_WAITALL);
    if(ret == -1) {
        LOG(INFO,"recv error\n");
    }
    ret = recv(net_fd,buf,len,MSG_WAITALL);
    if(ret == -1) {
        LOG(INFO,"recv errot\n");
    }

    //获取用户当前工作目录
    user_t user;
    select_user(mysql,pclient_info->name,&user);
    int file_id = user.file_id;

    char query[512] = {0};  //用来放SQL语句的字符串
    MYSQL_RES *result;
    MYSQL_ROW row;                                                         

    char mysql_file_path[300] = {0};

    sprintf(query,"select file_path from file where file_id = %d",file_id);
    if(mysql_query(mysql,query)){
        fprintf(stderr,"query error:%s\n",mysql_error(mysql));
        return -1;
    }
    //接收处理结果集
    result = mysql_store_result(mysql);
    ERROR_CHECK(result,NULL,"mysql_store_result");
    if((row = mysql_fetch_row(result))){
        strcat(mysql_file_path,row[0]);
    }
    mysql_free_result(result);



    user_t user_res;
    bzero(&user_res, sizeof(user_res));
    int ret2 = select_user(mysql, pclient_info->name, &user_res);
    if(ret2 == -1){
        printf("根据用户名查找user表出错！\n");
        return -1;
    }

    file_t file_path_res;
    bzero(&file_path_res, sizeof(file_path_res));
    int ret3 = fileid_select_file(mysql, user_res.file_id, &file_path_res);
    if(ret3 == -1){
        printf("查找file表出错！\n");
        return -1;
    }
    if(file_path_res.parent_id == -1){
        strcat(mysql_file_path,buf);
    }else{
        strcat(mysql_file_path,"/");
        strcat(mysql_file_path,buf);
    }

    puts(mysql_file_path);

    bzero(query,512);
    sprintf(query,"select f.file_name,file_hash from user u join file f on u.user_id = f.user_id where u.user_name = '%s' and f.file_path = '%s'",pclient_info -> name,mysql_file_path);
    ret = mysql_query(mysql,query);
    if(ret != 0) {
        LOG(ERROR,"mysql_query\n");
    }
    result = mysql_store_result(mysql);
    int is_file = 0;
    char file_hash[256] = {0};


    while((row = mysql_fetch_row(result))) {
        if(strcmp(buf,row[0]) == 0) {
            is_file = 1;
            strcpy(file_hash,row[1]);
            break;
        }
    }
    puts(file_hash);
    mysql_free_result(result);
    //查找的文件不存在
    if(is_file == 0) {
        bzero(buf,sizeof(buf));
        strcpy(buf,"此文件不存在");
        puts(buf);
        len = strlen(buf);
        ret = send(net_fd,&len,sizeof(int),0);
        if(ret == -1)
            LOG(ERROR,"send failed\n");
        ret = send(net_fd,buf,len,0);
        if(ret == -1)
            LOG(ERROR,"send failed\n");
        return 0;
    }
    //找到了文件,告诉客户端
    bzero(buf,sizeof(buf));
    strcpy(buf,"文件已找到");
    len = strlen(buf);
    ret = send(net_fd,&len,sizeof(int),0);
    if(ret == -1) {
        LOG(ERROR,"send failed\n");
    }
    ret = send(net_fd,buf,len,0);
    if(ret == -1) {
        LOG(ERROR,"send failed\n");
    }

    char file_path[256] = "./file/";
    strcat(file_path,file_hash);
    int file_fd = open(file_path,O_RDONLY);
    if(file_fd == -1) {
        LOG(ERROR,"open failed\n");
        return 0;
    }

    //获取文件信息
    struct stat file_info;
    ret = fstat(file_fd,&file_info);
    if(ret == -1) {
        LOG(ERROR,"fstat failed\n");
        return 0;
    }
    len = sizeof(file_info.st_size);
    ret = send(net_fd,&len,sizeof(int),0);
    ret = send(net_fd,&file_info.st_size,len,0);

    ret = sendfile(net_fd,file_fd,NULL,file_info.st_size);
    if(ret == -1) {
        LOG(ERROR,"sendfile failed\n");
    }
    //关闭网络文件描述符
    close(pclient_info->net_fd);


}
// ！！！！！！！长命令需要在完成任务后在函数体内部关闭网络文件描述符！！！！！！！

//是否秒传文件
//返回值，0代表秒传成功，-1代表查询失败，-2代表用户在上传重复的文件
static int is_instant_transmit(client_info_t* pclient_info, MYSQL* mysql, char *file_hash){
    //和数据库中的hash值比较， 看是否已经存在此文件
    //定义用来存放查询结果的结构体
    file_t file_res;
    bzero(&file_res, sizeof(file_res));

    int ret = select_file(mysql, file_hash, &file_res);
    if(ret == -1){
        printf("根据hash值查找file表出错！不满足秒传条件，开始传送文件...\n");
        return -1;
    }

    //查到数据了，证明服务器中已经有需要上传的文件了
    //判断当前路径下是否已经存在该文件

    //准备需要插入的数据
    //准备file_name
    char file_name[256] = {0};
    strcpy(file_name, file_res.file_name);

    //准备parent_id, 即为user表中的file_id
    int parent_id = 0;
    user_t user_res;
    bzero(&user_res, sizeof(user_res));
    int ret2 = select_user(mysql, pclient_info->name, &user_res);
    if(ret2 == -1){
        printf("根据用户名查找user表出错！\n");
        return -1;
    }
    parent_id = user_res.file_id;

    //准备user_id
    int user_id = user_res.user_id;

    //准备file_path
    char file_path[512];
    file_t file_path_res;
    bzero(&file_path_res, sizeof(file_path_res));
    int ret3 = fileid_select_file(mysql, parent_id, &file_path_res); 
    if(ret3 == -1){
        printf("查找file表出错！\n");
        return -1;
    }
    //判断插入文件的父目录是不是根目录
    //需要根据parent_id去查表，看父目录的parent_id是否等于-1
    //拼接查询语句的sql
    if(file_path_res.parent_id == -1){
        //说明是在根目录下插入数据
        sprintf(file_path, "%s%s", file_path_res.file_path, file_name);
    }else{
        sprintf(file_path, "%s/%s", file_path_res.file_path, file_name);
    }
    //printf("file_path_res.file_path = %s\n", file_path_res.file_path);
    //printf("fiel_path = %s\n", file_path);

    //判断当前路径下是否有该文件
    //需要先根据用户名去查找user表，拿到file_id，然后根据file_id查找file表
    //
    char is_exist_file_sql[256];
    sprintf(is_exist_file_sql, "select * from file where parent_id = %d;", user_res.file_id);
    mysql_query(mysql, is_exist_file_sql);
    // 获得查询的结果
    MYSQL_RES * res2 = mysql_store_result(mysql);
    if(res2 == NULL){
        printf("在判断用户是否重复上传时查询失败，查询结果集为NULL\n");
        return -1;
    }
    unsigned int row_num2 = mysql_num_rows(res2);
    if(row_num2 == 0){
        //查询的结果行数为0，要么查询出错，要么用户在根目录
        printf("在判断用户是否重复上传时查询失败，查询到0行结果\n");
    }
    MYSQL_ROW row2;

    while((row2 = mysql_fetch_row(res2)) != NULL){
        // 遍历一行的file_hash是否和我需要上传的这个文件的hash相同
        //row[4]为文件的hash值，如果是目录文件，则哈希值为NULL
        //printf("file_name = %s\n", file_name);
        //printf("row[2] = %s\n", row2[2]);
        //printf("row[7] = %s\n", row2[7]);
        //printf("row[8] = %s\n", row2[8]);
        if(row2[4] != NULL){//跳过目录
            if(strcmp(file_name, row2[2]) == 0 && strcmp(row2[7], "0") == 0 && strcmp(row2[8], "0") == 0){
                //该目录下已经有同名文件了，用户在重复上传
                return -2;
            }
        }
    }
    mysql_free_result(res2);

    //走到这里，说明用户没有重复上传，并且在服务器中找到了需要上传的文件，插入新的数据即可
    //拼接插入数据的sql
    char insert_sql[4096] = {0};
    sprintf(insert_sql, "insert into file values (NULL, %d, '%s', %d, '%s', '-', '%s', 0, 0);", 
            parent_id,
            file_name, 
            user_id,
            file_hash,
            file_path);
    //printf("通过极速秒传插入数据，插入的SQL为%s\n", insert_sql);
    mysql_query(mysql, insert_sql);

    return 0;
}

//判断是否需要续传
//is_bad 判断当前上传的文件是否是上次断掉的文件                                         
static int is_need_continuation(MYSQL* mysql, client_info_t* pclient_info, int *is_bad, char *file_hash){
    //先根据用户名去查user表，找到file_id，
    //然后根据file_id去查file表，找到这一条数据，在这一条数据里拿到parent_id
    //然后根据parent_id去查所有文件，如果找到了当前上传的文件就代表是续传，否则不是
    user_t user;
    bzero(&user, sizeof(user));
    select_user(mysql, pclient_info->name, &user);
    file_t file;
    bzero(&file, sizeof(file));
    fileid_select_file(mysql, user.file_id, &file);
    char select_sql[2048] = {0};
    sprintf(select_sql, "select * from file where parent_id = %d", file.parent_id);
    mysql_query(mysql, select_sql);
    // 获得查询的结果
    MYSQL_RES * res = mysql_store_result(mysql);
    if(res == NULL){
        printf("在判断用户是否续传时，查询结果集为NULL\n");
        return -1;
    }
    unsigned int row_num = mysql_num_rows(res);
    if(row_num == 0){
        //查询的结果行数为0，要么查询出错，要么用户在根目录
        printf("在判断用户是否续传时，查询到0行结果，证明当前目录下没有文件\n");
        *is_bad = 0;
        return -1;
    }
    MYSQL_ROW row;
    while((row = mysql_fetch_row(res)) != NULL){
        if(row[4] != NULL){
            if(strcmp(row[4], file_hash) == 0){
                //证明正在做续传
                printf("查表得知证明在做续传，修改is_bad的值为1\n");
                *is_bad = 1;
                return 0;
            } 
        }
    }

    return -1;
}


//puts服务端接收文件
//is_bad 0代表文件正常 1代表文件损坏
static bool puts_recvfile(client_info_t* pclient_info, char *file_hash, int *is_bad){

    //接收文件前先计算文件的hash值，看文件是否是已经损坏的文件
    char before_recvfile_hash[41] = {0};
    hash_sha1(before_recvfile_hash, file_hash);                
    //printf("file_hash = %s\n", file_hash);
    //printf("file_hash_now = %s\n", before_recvfile_hash);
    if(strcmp(before_recvfile_hash, file_hash) != 0){
        *is_bad = 1;
    }        


    //存放路径path
    //拼接文件所在位置
    char filePath[1024] = {0};
    sprintf(filePath, "%s%s", "./file/", file_hash);

    int file_fd = open(filePath, O_RDWR | O_CREAT, 0666);
    if(file_fd == -1){
        printf("创建文件失败\n");
    }
    //printf("需要下载的文件路径为%s\n", filePath);

    //接收文件大小
    off_t file_size = 0;
    recv(pclient_info->net_fd, &file_size, sizeof(off_t), MSG_WAITALL);
    //printf("client: file_size: %ld \n", file_size);

    //给客户端发送当前文件指针位置
    lseek(file_fd,0,SEEK_END);
    off_t location = lseek(file_fd,0,SEEK_CUR);
    location -= location % 4096; 
    send(pclient_info -> net_fd,&location,sizeof(off_t),MSG_NOSIGNAL);



    // 首先在mmap之前调整文件大小, 让文件足够大
    ftruncate(file_fd, file_size);

    // 接收数据
    void *p = mmap(NULL, file_size, PROT_READ|PROT_WRITE, MAP_SHARED, file_fd, location);
    recv(pclient_info->net_fd, p, file_size - location, MSG_WAITALL);
    //puts("接收完成");
    munmap(p, location);
    close(file_fd);

    //再次判断接收到的文件的hash值，看文件是否只传了一半
    char after_recvfile_hash[41] = {0};
    hash_sha1(after_recvfile_hash, file_hash);    
    //printf("file_hash = %s\n", file_hash);
    //printf("file_hash_now = %s\n", after_recvfile_hash);
    if(strcmp(after_recvfile_hash, file_hash) != 0){
        *is_bad = 1;
    }
    return true;
}

//上传了一个新文件，新建一条新数据
static bool insert_newfile(client_info_t* pclient_info, MYSQL* mysql, char *file_hash, int is_bad){
    printf("进入插入数据的函数\n");
    //准备要插入的数据
    //准备parent_id
    //准备parent_id, 即为user表中的file_id
    int parent_id = 0;
    user_t user_res;
    bzero(&user_res, sizeof(user_res));
    int ret2 = select_user(mysql, pclient_info->name, &user_res);
    if(ret2 == -1){
        printf("在上传完文件之后，准备插入数据，根据用户名查找file表出错！\n");
        return false;
    }
    parent_id = user_res.file_id;

    //准备file_name
    //由于用户传来的参数可能携带路径信息，所以需要对路径进行切割，找到真正的文件名
    char *token = strtok(pclient_info->argument, "/");
    char *file_name = NULL;
    while(token != NULL){
        file_name = token;//每次保存最新的部分
        token = strtok(NULL, "/");
    }
    //printf("文件名为%s\n", file_name);


    //准备user_id
    int user_id = user_res.user_id;

    //准备file_path       
    char file_path[256];
    file_t file_path_res; 
    bzero(&file_path_res, sizeof(file_path_res));                             
    int ret3 = fileid_select_file(mysql, parent_id, &file_path_res);          
    if(ret3 == -1){       
        printf("查找file表出错！\n");
        return false;     
    }                     
    //判断插入文件的父目录是不是根目录
    //需要根据parent_id去查表，看父目录的parent_id是否等于-1
    //未做。。。。。
    //拼接查询语句的sql
    if(file_path_res.parent_id == -1){
        //说明是在根目录下插入数据
        sprintf(file_path, "%s%s", file_path_res.file_path, file_name);
    }else{
        sprintf(file_path, "%s/%s", file_path_res.file_path, file_name);
    }

    //准备bad_file和cancel_flag
    int bad_file = 0;
    int cancel_flag = 0;

    int ret = is_need_continuation(mysql, pclient_info, &is_bad, file_hash);
    if(is_bad > 0 && ret == 0){
        //拼接插入数据的sql语句
        printf("is_bad = %d\n",is_bad);
        printf("ret = %d\n", ret);
        char update_sql[1024] = {0};
        sprintf(update_sql, "update file set cancel_flag = 0 ,bad_file = 0 where user_id = %d and file_hash = '%s' and bad_file = 1 ",
                user_id,
                file_hash);

        printf("需要更新数据的sql语句为%s\n", update_sql);
        mysql_query(mysql, update_sql);
        return true;
    }
    //拼接插入数据的sql语句
    char insert_sql[1024] = {0};
    sprintf(insert_sql, "insert into file values (NULL, %d, '%s', %d, '%s', '-', '%s', %d, %d)", 
            parent_id,
            file_name,
            user_id,
            file_hash,
            file_path,
            cancel_flag,
            bad_file); 
    printf("需要插入数据的sql语句为%s\n", insert_sql);
    mysql_query(mysql, insert_sql);

    return true;
}

